﻿using System;
using System.Collections.Generic;
using System.Linq;

namespace Kernelx.Extensions._Internals.Clauses
{
    using ClauseElement2Expressions;
    using Kernelx.Common.Clauses;
    using Kernelx.Extensions;
    using System.Collections;
    using System.Linq.Expressions;
    using System.Reflection;

    internal class ClauseElement2Expression
    {
        private static readonly Dictionary<ClauseOperator, ICreateExpressionMetadata> _container = new Dictionary<ClauseOperator, ICreateExpressionMetadata>();

        private static readonly MethodInfo _enumerable_Contains = typeof(Enumerable).GetMethods().First(x => x.Name == "Contains" && x.GetParameters().Length == 2);
        private static readonly MethodInfo _str_Contains = typeof(string).GetMethod("Contains", new[] { typeof(string) });
        private static readonly MethodInfo _startsWith = typeof(string).GetMethod("StartsWith", new[] { typeof(string) });
        private static readonly MethodInfo _endsWith = typeof(string).GetMethod("EndsWith", new[] { typeof(string) });

        static ClauseElement2Expression()
        {
            _container[ClauseOperator.Equal] = new CreateExpressionMetadata(Expression.Equal);
            _container[ClauseOperator.NotEqual] = new CreateExpressionMetadata(Expression.NotEqual);
            _container[ClauseOperator.GreaterThan] = new CreateExpressionMetadata(Expression.GreaterThan);
            _container[ClauseOperator.LessThan] = new CreateExpressionMetadata(Expression.LessThan);
            _container[ClauseOperator.ThanOrEqual] = new CreateExpressionMetadata(Expression.GreaterThanOrEqual);

            _container[ClauseOperator.LessThanOrEqual] = new CreateExpressionMetadata(Expression.LessThanOrEqual, (value, propType) =>
            {
                value = (propType == typeof(DateTime) || propType == typeof(DateTime?)) && value.ToString().Length == 10 ?
                Convert.ToDateTime(value.ToString()).AddDays(1) : value;
                return Expression.Constant(value, propType);
            });

            _container[ClauseOperator.Contains] = new CreateExpressionMetadata((member, constant) =>
            {
                // p => value.Contains(p.propertyName) 
                if (constant is NewArrayExpression lstConstant)
                {
                    var elementType = (lstConstant.Expressions.First() as ConstantExpression).Value.GetType();
                    var contains_Method = _enumerable_Contains.MakeGenericMethod(elementType);
                    return Expression.Call(null, contains_Method, lstConstant, member);
                }
                // p => p.propertyName.Contains(value)
                return Expression.Call(member, _str_Contains, constant);
            },
            (value, propType) =>
            {
                var valueType = value.GetType();
                if (valueType != typeof(string) && (valueType.IsArray || value is IEnumerable))
                {
                    var elementType = valueType.IsArray ? valueType.GetElementType() : valueType.GetGenericArguments()[0];
                    IList<ConstantExpression> constants = new List<ConstantExpression>();
                    var iterator = (value as IEnumerable).GetEnumerator();
                    while (iterator.MoveNext())
                        constants.Add(Expression.Constant(iterator.Current, elementType));
                    return Expression.NewArrayInit(elementType, constants.ToArray());
                }
                return Expression.Constant(value, typeof(string));
            });

            _container[ClauseOperator.NotContains] = new NestCreateExpressionMetadata(Expression.Not, _container[ClauseOperator.Contains]);

            _container[ClauseOperator.StartsWith] = new CreateExpressionMetadata((member, constant) => Expression.Call(member, _startsWith, constant));
            _container[ClauseOperator.EndsWith] = new CreateExpressionMetadata((member, constant) => Expression.Call(member, _endsWith, constant));

        }



        private static MemberExpression GetPropertyNameExpression(string propertyName, ParameterExpression parameter)
        {
            var lastIndex = propertyName.LastIndexOf('.');
            if (lastIndex == -1)
                return Expression.PropertyOrField(parameter, propertyName);
            else
            {
                var parentPropertyName = propertyName.Substring(0, lastIndex);
                propertyName = propertyName.Substring(lastIndex + 1);
                var parent = Expression.PropertyOrField(parameter, parentPropertyName);
                return Expression.PropertyOrField(parent, propertyName);
            }
        }

        private static Expression CreateExpressionBody(ClauseOperator @operator, string propertyName, object value, ParameterExpression parameter)
        {
            var memberProperty = GetPropertyNameExpression(propertyName, parameter);

            var propType = (memberProperty.Member as PropertyInfo).PropertyType;
            if (value != null)
            {
                var vType = value.GetType();
                if (vType != propType && vType.IsDerived(propType))
                    value = value.ConvertTo(propType);
            }
            var metadata = _container[@operator];
            return metadata.CreateBody(memberProperty, metadata.CreateConstant(value, propType));
        }

        private static Expression CreateExpressionBody(ClauseMerge @operator, IClauseElement left, IClauseElement right, ParameterExpression parameter)
        {
            Expression left_expr = CreateExpressionBody(left, parameter);
            Expression right_expr = CreateExpressionBody(right, parameter);
            return @operator == ClauseMerge.And ? Expression.AndAlso(left_expr, right_expr) : Expression.OrElse(left_expr, right_expr);
        }

        private static Expression CreateExpressionBody(IClauseElement clause, ParameterExpression parameter)
        {
            if (clause is ClauseElement element)
                return CreateExpressionBody(element.Operator, element.PropName, element.Value, parameter);
            else if (clause is MergeClauseElement merge)
                return CreateExpressionBody(merge.Merge, merge.Left, merge.Right, parameter);
            return null;
        }

        internal static LambdaExpression CreateLambdaExpression(IClauseElement clause, Type targetType, ParameterExpression parameter = null)
        {
            if (clause != null)
            {
                //创建节点变量如p=>的节点p 
                parameter = parameter ?? Expression.Parameter(targetType, "p");
                var delegateType = typeof(Func<,>).MakeGenericType(new[] { targetType, typeof(bool) });
                return Expression.Lambda(delegateType, CreateExpressionBody(clause, parameter), parameter);
            }
            return null;
        }

    }
}
